--SLA Declaration
def bestQoS(comparer,publisher) = head(sortBy(comparer,publisher))
def addNum(x, y) = x + y
def zipWith(_, [], _) = []
def zipWith(_, _, []) = []
def zipWith(f, x:xs, y:ys) = f(x, y) : zipWith(f, xs, ys)

-- Types and Definitions for Response Time
def LatencyIncrement(sitex) =
  {- Using tuple construction as fork-join -}
  (sitex, Rtime()) >(sitex, st)> (sitex, Rtime()-st)
type TimeUnit = Second() | Millisecond()

def class ResponseTime(unit) =
  def QoS(sitex) =
    LatencyIncrement(sitex) >(_, q)>
    (Ift(unit = Millisecond) >> q | Ift(unit = Second) >> q/1000)
  def QoSOplus(rt1, rt2) = rt1+rt2
  def QoSCompare(rt1, rt2) = rt1 <= rt2
  def QoSCompete(rt1, rt2) = bestQoS(QoSCompare, [rt1, rt2])
  def QoSVee(rt1, rt2) = max(rt1, rt2)
  stop

-- Types and Definitions for Cost
type CostUnit = Items() | CurrencyEuros() | CurrencyDollars()
val c = Random(50)
def class Cost(unit) =
  def UnitConverter(x, Items()) = x
  def UnitConverter(x, CurrencyEuros()) = x*100
  def UnitConverter(x, CurrencyDollars()) = x*80
  def QoS(sitex, c) =
    val s = Ref([])
    signal >> (s? >q> (if q=[] then s := c >> s? 
     else QoSOplus(s?, c) >v> s := v >> UnitConverter(s?, unit)))
  def QoSOplus(c1, c2) = zipWith(addNum, c1, c2)
  def QoSCompare(c1, c2) = foldl((&&), true, zipWith((<=), c1, c2))
  def QoSCompete(c1, c2) = bestQoS(QoSCompare, [c1, c2])
  def QoSVee(c1, c2) = zipWith(max, c1, c2)
  stop
  
def class NonAmbient(QoStype) = 
  def QoSCompete(Number,Number) :: Number
  def QoSCompete(q1,q2) = head(sortBy((<:), [q1,q2]))
  signal

def class Ambient(QoStype,competition) = 
  def QoSCompete(Number,Number) :: Number
  def QoSCompete(q1,q2) = head(sortBy(competition,  [q1,q2]))  
  signal  

--QoS Registry 
val QoSRegistry = 
 [
  {. name = "TAgent", QoSDom = ResponseTime, QoSUnit = Millisecond, 
	Handle = LatencyIncrement .},
  {. name = "TAgent", QoSDom = Cost, QoSUnit = CurrencyEuros, 
	Handle = c .},  
  {. name = "Airline", QoSDom = Cost, QoSUnit = Items, 
	Handle = c .},  
  {. name = "Hotel", QoSDom = Cost, QoSUnit = Items,
	Handle = c  .}
 ]

def QoSMatch(siteID) = each(QoSRegistry) >M> Ift(M.name = siteID) 
  >> (M.QoSDom,M.QoSUnit,M.Handle)
  
 
--QoS Weaving
def QoSWeaver(site,(lookup,unit,handle)) =
  def ResponseTimeCheck(competition) = 
    Ift(lookup = ResponseTime && handle = LatencyIncrement) >> 
     (Ambient(ResponseTime,competition) 
     >> (ResponseTime(unit).QoS(site)))    
  def CostCheck() =
    Ift(lookup = Cost && handle = c) >> 
     (NonAmbient(Cost) >> Cost(unit).QoS(site,c))
  signal >> v<v<(ResponseTimeCheck(max)| CostCheck()) 

def QoS(site,identifier) = 
  val Data = Ref()
  def QoSCollect(v) = collect(defer2(QoSWeaver,Data?,v))
   site >d> Data:=d >> collect(defer(QoSMatch,identifier)) >v> 
   (Data?, map(QoSCollect,v))
 
--TravelAgent2 Example
val AirlineList = ["Airline 1", "Airline 2"]
val HotelList = ["Hotel A", "Hotel B"]
def QoSsite(sitex) = QoS(sitex.function(),sitex.QoSID())
def bestQ(comparer,publisher) =  head(sortBy(comparer,collect(publisher)))

--Simulation utilities
def cat() = x <x< ("Economy" | "Premium")
val simElaspedTime = Rclock()

--Functional Sites declared
def GenerateOrder(SalesOrder,Budget) = Dictionary() >GenerateInvoice> 
  GenerateInvoice.TravelAgent := SalesOrder.ordernumber? >> 
   GenerateInvoice.acceptedTime := simElaspedTime.time() >> 
   Println("Order "+GenerateInvoice.TravelAgent?+" accepted at time "
   +GenerateInvoice.acceptedTime?) >> (GenerateInvoice,Budget)
	
def inquireCost(List) = each(List) >sup> Dictionary() >ProductDetails> 
  ProductDetails.Company := sup>> ProductDetails.cost := Random(50) 
   >> ProductDetails
def inquireCategory(List) = each(List) >sup> Dictionary() >ProductDetails> 
  ProductDetails.Company := sup >> ProductDetails.cost := Random(50) >> 
   ProductDetails.category := cat() >> ProductDetails
	
def compareCost(x, y) = x.cost? <= y.cost?
def compareCategory(x, y) = if x.category?="Economy" then false 
  else if y.category?="Economy" then true else compareCost(x, y)
	
def CheckBudget(GenerateInvoice,Budget) = 
  if (GenerateInvoice.AirQuote?.cost? + GenerateInvoice.HotelQuote?.cost? 
   <: Budget) then GenerateInvoice else 
   (Println("Resubmit Order " +GenerateInvoice.TravelAgent?) >> 
   Dictionary() >SalesOrder> 
   SalesOrder.ordernumber:= GenerateInvoice.TravelAgent? 
   >> (SalesOrder,GenerateOrder(SalesOrder,Budget)))
def timeout(x, t, SalesOrder) = Let(Some(x) 
  | (Rwait(t) >> notifyFail(SalesOrder, "Timeout") >> None()))
def notifyFail(SalesOrder, reaSalesOrdern) = 
  Println("Order "+SalesOrder.id?+" failed: "+reaSalesOrdern) 
   >> stop

--QoS-Aware Sites
def class AirlineCompany(GenerateInvoice,Cost) =
def function() = bestQ(compareCost, defer(inquireCost,AirlineList)) 
   >q> GenerateInvoice >> GenerateInvoice.AirQuote := q
  def QoSID() =  "Airline"
  stop

def class HotelBooking(GenerateInvoice,Cost) = 
def function() = bestQ(compareCategory, defer(inquireCategory,HotelList)) 
   >q> GenerateInvoice >> GenerateInvoice.HotelQuote := q
  def QoSID() =  "Hotel"
  stop
          
def class TravelAgent(SalesOrder,Budget,Cost,Latency) = 
def function() =timeout((GenerateOrder(SalesOrder,Budget) 
  >(GenerateInvoice,Budget)> QoSsite(AirlineCompany(GenerateInvoice,Cost)) 
   >(_,AirQoS)> GenerateInvoice.AirQoS := AirQoS 
   >> QoSsite(HotelBooking(GenerateInvoice,Cost)) >(_,HotelQoS)> 
   GenerateInvoice.HotelQoS := HotelQoS 
   >> CheckBudget(GenerateInvoice,Budget)) , 10000, SalesOrder) 
   >Some(GenerateInvoice)> GenerateInvoice
  def QoSID()= "TAgent"
  stop

--Simulation  
def simulateOrders(50) = stop
def simulateOrders(n) = Dictionary() >SalesOrder> SalesOrder.ordernumber:= n 
  >> SalesOrder
  | Rwait(Random(100)) >> simulateOrders(n+1)
   
simulateOrders(1) >SalesOrder> 
  QoSsite(TravelAgent(SalesOrder,150,Cost,ResponseTime)) 
  >(GenerateInvoice,QoS)> ((GenerateInvoice.TravelAgent?,QoS) 
   >> Println("Invoice for order "+SalesOrder.ordernumber?+"presented at time "+simElaspedTime.time()) 
   >> (GenerateInvoice.AirQoS?,GenerateInvoice.HotelQoS?) 
   >([[aq]],[[hq]])> (aq,hq) 
   >> (GenerateInvoice.TravelAgent?,QoS))
